/**
 * Copyright (c) 2011 eelpo.com
 * All rights reserved.
 */
package com.eelpo.common.encoder;

import java.io.*;

/**
 * BASE64算法编码类（把3个8位字节（3*8=24）转化为4个6位的字节（4*6=24）,然后编码）
 * 
 * @author <a href="mailto:Brian@eelpo.com">汪洪州</a>
 * @since 1.0
 */

public class BASE64EncoderStream extends FilterOutputStream {

	/**
	 * 缓存一次处理的三个字节
	 */
	private byte[] buffer;

	/**
	 * 缓存处理中字节的个数
	 */
	private int bufsize = 0;

	/**
	 * 记录当前行写入的字节数，用户判断是否超过最大长度
	 */
	private int count = 0;

	/**
	 * 每行输出字节的个数
	 */
	private int bytesPerLine;

	/**
	 * BASE64（RFC1521）的64个字符集
	 */
	private final static char PEM_ARRAY[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
			'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
			'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2
			'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3
			'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4
			'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5
			'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6
			'4', '5', '6', '7', '8', '9', '+', '/' // 7
	};

	/**
	 * 指定输入流构造BASE64编码类,默认每行输出64个字节
	 * 
	 * @param out
	 *            输出流
	 */
	public BASE64EncoderStream(OutputStream out) {
		this(out, 76);
	}

	/**
	 * 指定输入流和每行字节数构造BASE64编码类
	 * 
	 * @param out
	 *            输出流
	 * @param bytesPerLine
	 *            每行字节数
	 */
	public BASE64EncoderStream(OutputStream out, int bytesPerLine) {
		super(out);

		// 初始化三个字节长度，用于保存一次处理的三个字节
		buffer = new byte[3];

		// 保存输入流一行输出的字节数
		this.bytesPerLine = bytesPerLine;
	}

	/**
	 * 进行BASE64编码
	 * 
	 * @param inbuf
	 *            需要编码的字节
	 * @return 返回编码过的字节
	 */
	public static byte[] encodeBuffer(byte[] inbuf) {

		// 判断输入的字节数长度是否为0
		if (inbuf.length == 0)
			return inbuf;

		// 计算编码之后的字节长度,+2是为了取模之后余数的舍入。
		byte[] outbuf = new byte[((inbuf.length + 2) / 3) * 4];

		// 记录当前处理字节的位置，和输入字节的位置
		int inpos = 0, outpos = 0;

		// 记录待处理的字节长度
		int size = inbuf.length;

		// 循环进行移位和编码，每次循环处理3个字节
		while (size > 0) {
			byte a, b, c;

			if (size == 1) {

				// 循环处理之后余数为1的时候，处理最后一个字节并填充两个等于符号
				a = inbuf[inpos++];
				b = 0;
				c = 0;

				// 处理剩余的一个字节
				outbuf[outpos++] = (byte) PEM_ARRAY[(a >>> 2) & 0x3F];
				outbuf[outpos++] = (byte) PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)];

				// 填充等于符号
				outbuf[outpos++] = (byte) '=';
				outbuf[outpos++] = (byte) '=';

			} else if (size == 2) {

				// 循环处理之后余数为2的时候，处理最后两个字节并填充一个等于符号
				a = inbuf[inpos++];
				b = inbuf[inpos++];
				c = 0;

				// 处理剩余的两个字节
				outbuf[outpos++] = (byte) PEM_ARRAY[(a >>> 2) & 0x3F];
				outbuf[outpos++] = (byte) PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)];
				outbuf[outpos++] = (byte) PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)];

				// 填充等于符号
				outbuf[outpos++] = (byte) '=';

			} else {
				// 一次取三个字节进行处理
				a = inbuf[inpos++];
				b = inbuf[inpos++];
				c = inbuf[inpos++];

				outbuf[outpos++] = (byte) PEM_ARRAY[(a >>> 2) & 0x3F];
				outbuf[outpos++] = (byte) PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)];
				outbuf[outpos++] = (byte) PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)];
				outbuf[outpos++] = (byte) PEM_ARRAY[c & 0x3F];
			}

			// 字节长度每次递减三
			size -= 3;
		}
		return outbuf;
	}

	public static String encode(byte[] inbuf) {
		return new String(encodeBuffer(inbuf));
	}

	/**
	 * 编码字节数组中的三个字节
	 * 
	 * @throws IOException
	 *             抛出IO异常
	 */
	private void encode() throws IOException {

		// 判断是否超过每行的最大长度
		if (count + 4 > bytesPerLine) {
			out.write('\r');
			out.write('\n');
			count = 0;
		}

		byte a, b, c;
		if (bufsize == 1) {

			a = buffer[0];
			b = 0;
			c = 0;

			// 处理剩余的一个字节
			out.write(PEM_ARRAY[(a >>> 2) & 0x3F]);
			out.write(PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);

			// 填充等于符号
			out.write('=');
			out.write('=');
		} else if (bufsize == 2) {

			a = buffer[0];
			b = buffer[1];
			c = 0;

			// 处理剩余的两个字节
			out.write(PEM_ARRAY[(a >>> 2) & 0x3F]);
			out.write(PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
			out.write(PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);

			// 填充等于符号
			out.write('='); // pad character
		} else {

			// 一次取三个字节进行处理
			a = buffer[0];
			b = buffer[1];
			c = buffer[2];

			out.write(PEM_ARRAY[(a >>> 2) & 0x3F]);
			out.write(PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
			out.write(PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
			out.write(PEM_ARRAY[c & 0x3F]);
		}

		count += 4;
	}

	/**
	 * 
	 * 将字节写入输出流
	 * 
	 * @param b
	 *            写入输出流的字节数组
	 * @param off
	 *            偏移量
	 * @param len
	 *            需要处理的字节长度
	 * @throws IOExcpetion
	 *             抛出IO异常
	 */
	public void write(byte[] b, int off, int len) throws IOException {
		for (int i = 0; i < len; i++)
			write(b[off + i]);
	}

	/**
	 * 将字节写入输出流，默认处理整个字节数组
	 * 
	 * @param b
	 *            写入输出流的字节
	 * @throws IOExcpetion
	 *             抛出IO异常
	 */
	public void write(byte[] b) throws IOException {
		write(b, 0, b.length);
	}

	/**
	 * @param c
	 *            写入输出流的int数字
	 * @throws IOExcpetion
	 *             抛出IO异常
	 */
	public void write(int c) throws IOException {
		buffer[bufsize++] = (byte) c;
		if (bufsize == 3) {
			encode();
			bufsize = 0;
		}
	}

	/**
	 * 将缓冲的数据写入输出流
	 * 
	 * @throws IOExcpetion
	 *             抛出IO异常
	 */
	public void flush() throws IOException {
		if (bufsize > 0) {
			encode();
			bufsize = 0;
		}
		out.flush();
	}

	/**
	 * 关闭输出流
	 * 
	 * @throws IOExcpetion
	 *             抛出IO异常
	 */
	public void close() throws IOException {
		flush();
		out.close();
	}
}
